/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
import assert from 'assert';
import { GitHubPullRequestTitleAndDescriptionGenerator } from '../../src/extension/prompt/node/githubPullRequestTitleAndDescriptionGenerator';
import { CancellationToken } from '../../src/util/vs/base/common/cancellation';
import { IInstantiationService } from '../../src/util/vs/platform/instantiation/common/instantiation';
import { ssuite, stest } from '../base/stest';

function assertNoFakeIssue(result: string) {
	assert(!result.includes('#1234'));
}

ssuite({ title: 'PR Title and Description', location: 'context' }, () => {
	stest({ description: 'Multiple commits without issue information' }, async (testingServiceCollection) => {
		const accessor = testingServiceCollection.createTestingAccessor();
		const instantiationService = accessor.get(IInstantiationService);
		const generator = instantiationService.createInstance(GitHubPullRequestTitleAndDescriptionGenerator);
		const commitMessages = ['Tree Sticky Scroll', "Updated StickyScrollController and TreeRenderer classes for improved\nhandling of tree node indices and rendered elements", 'Merge branch \'main\' into benibenj/treeStickyScroll', 'Updated TreeNodeListMouseController to handle sticky elements and improve expand/collapse behavior', ':lipstick:', 'fix max-ratio corner case', 'Updated various components for better visibility … refactored code for readability and efficiency.', ':lipstick:', 'Refactor StickyScrollController and update CSS selectors in tree.css', 'Don\'t keep empty arrays around', 'SCM InputRenderer', 'SCM Action Button', 'Resource Markers', 'compressed explorer view', 'handle disposable', ':lipstick:', ':lipstick:', 'Merge branch \'main\' into benibenj/treeStickyScroll', ':lipstick:', 'fix colors'];

		const patches = [
			"@@ -227,6 +227,7 @@ export interface IListView<T> extends ISpliceable<T>, IDisposable {\n \treadonly renderHeight: number;\n \treadonly scrollHeight: number;\n \treadonly firstVisibleIndex: number;\n+\treadonly firstMostlyVisibleIndex: number;\n \treadonly lastVisibleIndex: number;\n \tonDidScroll: Event<ScrollEvent>;\n \tonWillScroll: Event<ScrollEvent>;\n@@ -753,16 +754,21 @@ export class ListView<T> implements IListView<T> {\n \n \tget firstVisibleIndex(): number {\n \t\tconst range = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);\n-\t\tconst firstElTop = this.rangeMap.positionAt(range.start);\n-\t\tconst nextElTop = this.rangeMap.positionAt(range.start + 1);\n+\t\treturn range.start;\n+\t}\n+\n+\tget firstMostlyVisibleIndex(): number {\n+\t\tconst firstVisibleIndex = this.firstVisibleIndex;\n+\t\tconst firstElTop = this.rangeMap.positionAt(firstVisibleIndex);\n+\t\tconst nextElTop = this.rangeMap.positionAt(firstVisibleIndex + 1);\n \t\tif (nextElTop !== -1) {\n \t\t\tconst firstElMidpoint = (nextElTop - firstElTop) / 2 + firstElTop;\n \t\t\tif (firstElMidpoint < this.scrollTop) {\n-\t\t\t\treturn range.start + 1;\n+\t\t\t\treturn firstVisibleIndex + 1;\n \t\t\t}\n \t\t}\n \n-\t\treturn range.start;\n+\t\treturn firstVisibleIndex;\n \t}\n \n \tget lastVisibleIndex(): number {",
			"@@ -256,8 +256,8 @@ export function isInputElement(e: HTMLElement): boolean {\n \treturn e.tagName === 'INPUT' || e.tagName === 'TEXTAREA';\n }\n \n-export function isMonacoEditor(e: HTMLElement): boolean {\n-\tif (e.classList.contains('monaco-editor')) {\n+function isListElementDescendantOfClass(e: HTMLElement, className: string): boolean {\n+\tif (e.classList.contains(className)) {\n \t\treturn true;\n \t}\n \n@@ -269,7 +269,27 @@ export function isMonacoEditor(e: HTMLElement): boolean {\n \t\treturn false;\n \t}\n \n-\treturn isMonacoEditor(e.parentElement);\n+\treturn isListElementDescendantOfClass(e.parentElement, className);\n+}\n+\n+export function isMonacoEditor(e: HTMLElement): boolean {\n+\treturn isListElementDescendantOfClass(e, 'monaco-editor');\n+}\n+\n+export function isMonacoCustomToggle(e: HTMLElement): boolean {\n+\treturn isListElementDescendantOfClass(e, 'monaco-custom-toggle');\n+}\n+\n+export function isActionItem(e: HTMLElement): boolean {\n+\treturn isListElementDescendantOfClass(e, 'action-item');\n+}\n+\n+export function isMonacoTwistie(e: HTMLElement): boolean {\n+\treturn isListElementDescendantOfClass(e, 'monaco-tl-twistie');\n+}\n+\n+export function isStickyScrollElement(e: HTMLElement): boolean {\n+\treturn isListElementDescendantOfClass(e, 'monaco-tree-sticky-row');\n }\n \n export function isButton(e: HTMLElement): boolean {\n@@ -1598,6 +1618,10 @@ export class List<T> implements ISpliceable<T>, IDisposable {\n \t\treturn this.view.firstVisibleIndex;\n \t}\n \n+\tget firstMostlyVisibleIndex(): number {\n+\t\treturn this.view.firstMostlyVisibleIndex;\n+\t}\n+\n \tget lastVisibleIndex(): number {\n \t\treturn this.view.lastVisibleIndex;\n \t}\n@@ -1887,10 +1911,18 @@ export class List<T> implements ISpliceable<T>, IDisposable {\n \t\treturn this.view.domNode;\n \t}\n \n+\tgetScrollableElement(): HTMLElement {\n+\t\treturn this.view.scrollableElementDomNode;\n+\t}\n+\n \tgetElementID(index: number): string {\n \t\treturn this.view.getElementDomId(index);\n \t}\n \n+\tgetElementTop(index: number): number {\n+\t\treturn this.view.elementTop(index);\n+\t}\n+\n \tstyle(styles: IListStyles): void {\n \t\tthis.styleController.style(styles);\n \t}",
			"@@ -4,7 +4,7 @@\n  *--------------------------------------------------------------------------------------------*/\n \n import { IDragAndDropData } from 'vs/base/browser/dnd';\n-import { $, append, clearNode, createStyleSheet, getWindow, h, hasParentWithClass, isActiveElement } from 'vs/base/browser/dom';\n+import { $, addDisposableListener, append, clearNode, createStyleSheet, getWindow, h, hasParentWithClass, isActiveElement } from 'vs/base/browser/dom';\n import { DomEmitter } from 'vs/base/browser/event';\n import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';\n import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';\n@@ -13,7 +13,7 @@ import { FindInput } from 'vs/base/browser/ui/findinput/findInput';\n import { IInputBoxStyles, IMessage, MessageType, unthemedInboxStyles } from 'vs/base/browser/ui/inputbox/inputBox';\n import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';\n import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';\n-import { IListOptions, IListStyles, isButton, isInputElement, isMonacoEditor, List, MouseController, TypeNavigationMode } from 'vs/base/browser/ui/list/listWidget';\n+import { IListOptions, IListStyles, isActionItem, isButton, isInputElement, isMonacoCustomToggle, isMonacoEditor, isStickyScrollElement, List, MouseController, TypeNavigationMode } from 'vs/base/browser/ui/list/listWidget';\n import { IToggleStyles, Toggle, unthemedToggleStyles } from 'vs/base/browser/ui/toggle/toggle';\n import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel';\n import { ICollapseStateChangeEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeEvent, ITreeFilter, ITreeModel, ITreeModelSpliceEvent, ITreeMouseEvent, ITreeNavigator, ITreeNode, ITreeRenderer, TreeDragOverBubble, TreeError, TreeFilterResult, TreeMouseEventTarget, TreeVisibility } from 'vs/base/browser/ui/tree/tree';\n@@ -26,7 +26,7 @@ import { SetMap } from 'vs/base/common/map';\n import { Emitter, Event, EventBufferer, Relay } from 'vs/base/common/event';\n import { fuzzyScore, FuzzyScore } from 'vs/base/common/filters';\n import { KeyCode } from 'vs/base/common/keyCodes';\n-import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';\n+import { Disposable, DisposableStore, dispose, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';\n import { clamp } from 'vs/base/common/numbers';\n import { ScrollEvent } from 'vs/base/common/scrollable';\n import { ISpliceable } from 'vs/base/common/sequence';\n@@ -327,7 +327,7 @@ class EventCollection<T> implements Collection<T>, IDisposable {\n \t}\n }\n \n-class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer<ITreeNode<T, TFilterData>, ITreeListTemplateData<TTemplateData>> {\n+export class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer<ITreeNode<T, TFilterData>, ITreeListTemplateData<TTemplateData>> {\n \n \tprivate static readonly DefaultIndent = 8;\n \n@@ -1172,6 +1172,406 @@ class FindController<T, TFilterData> implements IDisposable {\n \t}\n }\n \n+interface StickyScrollNode<T, TFilterData> {\n+\treadonly node: ITreeNode<T, TFilterData>;\n+\treadonly startIndex: number;\n+\treadonly endIndex: number;\n+\treadonly height: number;\n+\treadonly position: number;\n+}\n+\n+function stickyScrollNodeEquals<T, TFilterData, TRef>(node1: StickyScrollNode<T, TFilterData>, node2: StickyScrollNode<T, TFilterData>) {\n+\treturn node1.position === node2.position &&\n+\t\tnode1.node.element === node2.node.element &&\n+\t\tnode1.startIndex === node2.startIndex &&\n+\t\tnode1.height === node2.height &&\n+\t\tnode1.endIndex === node2.endIndex;\n+}\n+\n+class StickyScrollState<T, TFilterData, TRef> extends Disposable {\n+\n+\tconstructor(\n+\t\treadonly stickyNodes: StickyScrollNode<T, TFilterData>[] = []\n+\t) {\n+\t\tsuper();\n+\t}\n+\n+\tget count(): number { return this.stickyNodes.length; }\n+\n+\tequal(state: StickyScrollState<T, TFilterData, TRef>): boolean {\n+\t\treturn equals(this.stickyNodes, state.stickyNodes, stickyScrollNodeEquals);\n+\t}\n+\n+\taddDisposable(disposable: IDisposable): void {\n+\t\tthis._register(disposable);\n+\t}\n+}\n+\n+class StickyScrollController<T, TFilterData, TRef> extends Disposable {\n+\n+\tprivate maxNumberOfStickyElements: number;\n+\tprivate readonly maxWidgetViewRatio = 0.4;\n+\n+\tprivate readonly _widget: StickyScrollWidget<T, TFilterData, TRef>;\n+\n+\tprivate get firstVisibleNode() {\n+\t\tconst index = this.view.firstVisibleIndex;\n+\n+\t\tif (index < 0 || index >= this.view.length) {\n+\t\t\treturn undefined;\n+\t\t}\n+\n+\t\treturn this.view.element(index);\n+\t}\n+\n+\tconstructor(\n+\t\tprivate readonly tree: AbstractTree<T, TFilterData, TRef>,\n+\t\tprivate readonly model: ITreeModel<T, TFilterData, TRef>,\n+\t\tprivate readonly view: List<ITreeNode<T, TFilterData>>,\n+\t\trenderers: TreeRenderer<T, TFilterData, TRef, any>[],\n+\t\tprivate readonly treeDelegate: IListVirtualDelegate<ITreeNode<T, TFilterData>>,\n+\t\toptions: IAbstractTreeOptions<T, TFilterData> = {},\n+\t) {\n+\t\tsuper();\n+\n+\t\tconst stickyScrollOptions = this.validateStickySettings(options);\n+\t\tthis.maxNumberOfStickyElements = stickyScrollOptions.stickyScrollMaxItemCount;\n+\n+\t\tthis._widget = this._register(new StickyScrollWidget(view.getScrollableElement(), view, model, renderers, treeDelegate));\n+\n+\t\tthis._register(view.onDidScroll(() => this.update()));\n+\t\tthis._register(view.onDidChangeContentHeight(() => this.update()));\n+\t\tthis._register(tree.onDidChangeCollapseState(() => this.update()));\n+\n+\t\tthis.update();\n+\t}\n+\n+\tprivate update() {\n+\t\tconst firstVisibleNode = this.firstVisibleNode;\n+\n+\t\t// Don't render anything if there are no elements\n+\t\tif (!firstVisibleNode || this.tree.scrollTop === 0) {\n+\t\t\tthis._widget.setState(undefined);\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tconst stickyState = this.findStickyState(firstVisibleNode);\n+\t\tthis._widget.setState(stickyState);\n+\t}\n+\n+\tprivate findStickyState(firstVisibleNode: ITreeNode<T, TFilterData>): StickyScrollState<T, TFilterData, TRef> | undefined {\n+\t\tconst stickyNodes: StickyScrollNode<T, TFilterData>[] = [];\n+\t\tconst maximumStickyWidgetHeight = this.view.renderHeight * this.maxWidgetViewRatio;\n+\t\tlet firstVisibleNodeUnderWidget: ITreeNode<T, TFilterData> | undefined = firstVisibleNode;\n+\t\tlet stickyNodesHeight = 0;\n+\n+\t\tlet nextStickyNode = this.getNextStickyNode(firstVisibleNodeUnderWidget, undefined, stickyNodesHeight);\n+\t\twhile (nextStickyNode && stickyNodesHeight + nextStickyNode.height < maximumStickyWidgetHeight) {\n+\n+\t\t\tstickyNodes.push(nextStickyNode);\n+\t\t\tstickyNodesHeight += nextStickyNode.height;\n+\n+\t\t\tif (stickyNodes.length >= this.maxNumberOfStickyElements) {\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tfirstVisibleNodeUnderWidget = this.getNextVisibleNode(firstVisibleNodeUnderWidget);\n+\t\t\tif (!firstVisibleNodeUnderWidget) {\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tnextStickyNode = this.getNextStickyNode(firstVisibleNodeUnderWidget, nextStickyNode.node, stickyNodesHeight);\n+\t\t}\n+\n+\t\treturn stickyNodes.length ? new StickyScrollState(stickyNodes) : undefined;\n+\t}\n+\n+\tprivate getNextVisibleNode(node: ITreeNode<T, TFilterData>): ITreeNode<T, TFilterData> | undefined {\n+\t\tconst nodeIndex = this.getNodeIndex(node);\n+\t\tif (nodeIndex === -1 || nodeIndex === this.view.length - 1) {\n+\t\t\treturn undefined;\n+\t\t}\n+\t\tconst nextNode = this.view.element(nodeIndex + 1);\n+\t\treturn nextNode;\n+\t}\n+\n+\tprivate getNextStickyNode(firstVisibleNodeUnderWidget: ITreeNode<T, TFilterData>, previousStickyNode: ITreeNode<T, TFilterData> | undefined, stickyNodesHeight: number): StickyScrollNode<T, TFilterData> | undefined {\n+\t\tconst nextStickyNode = this.getAncestorUnderPrevious(firstVisibleNodeUnderWidget, previousStickyNode);\n+\t\tif (!nextStickyNode) {\n+\t\t\treturn undefined;\n+\t\t}\n+\n+\t\tif (nextStickyNode === firstVisibleNodeUnderWidget) {\n+\t\t\tif (!this.nodeIsUncollapsedParent(firstVisibleNodeUnderWidget)) {\n+\t\t\t\treturn undefined;\n+\t\t\t}\n+\n+\t\t\tif (this.nodeTopAlignsWithStickyNodesBottom(firstVisibleNodeUnderWidget, stickyNodesHeight)) {\n+\t\t\t\treturn undefined;\n+\t\t\t}\n+\t\t}\n+\n+\t\treturn this.createStickyScrollNode(nextStickyNode, stickyNodesHeight);\n+\t}\n+\n+\tprivate nodeTopAlignsWithStickyNodesBottom(node: ITreeNode<T, TFilterData>, stickyNodesHeight: number): boolean {\n+\t\tconst nodeIndex = this.getNodeIndex(node);\n+\t\tconst elementTop = this.view.getElementTop(nodeIndex);\n+\t\tconst stickyPosition = stickyNodesHeight;\n+\t\treturn this.view.scrollTop === elementTop - stickyPosition;\n+\t}\n+\n+\tprivate createStickyScrollNode(node: ITreeNode<T, TFilterData>, currentStickyNodesHeight: number): StickyScrollNode<T, TFilterData> {\n+\t\tconst height = this.treeDelegate.getHeight(node);\n+\t\tconst { startIndex, endIndex } = this.getNodeRange(node);\n+\n+\t\tconst position = this.calculateStickyNodePosition(endIndex, currentStickyNodesHeight);\n+\n+\t\treturn { node, position, height, startIndex, endIndex };\n+\t}\n+\n+\tprivate getAncestorUnderPrevious(node: ITreeNode<T, TFilterData>, previousAncestor: ITreeNode<T, TFilterData> | undefined = undefined): ITreeNode<T, TFilterData> | undefined {\n+\t\tlet currentAncestor: ITreeNode<T, TFilterData> = node;\n+\t\tlet parentOfcurrentAncestor: ITreeNode<T, TFilterData> | undefined = this.getParentNode(currentAncestor);\n+\n+\t\twhile (parentOfcurrentAncestor) {\n+\t\t\tif (parentOfcurrentAncestor === previousAncestor) {\n+\t\t\t\treturn currentAncestor;\n+\t\t\t}\n+\t\t\tcurrentAncestor = parentOfcurrentAncestor;\n+\t\t\tparentOfcurrentAncestor = this.getParentNode(currentAncestor);\n+\t\t}\n+\n+\t\tif (previousAncestor === undefined) {\n+\t\t\treturn currentAncestor;\n+\t\t}\n+\n+\t\treturn undefined;\n+\t}\n+\n+\tprivate calculateStickyNodePosition(lastDescendantIndex: number, stickyRowPositionTop: number): number {\n+\t\tlet lastChildRelativeTop = this.view.getRelativeTop(lastDescendantIndex);\n+\n+\t\t// If the last descendant is only partially visible at the top of the view, getRelativeTop() returns null\n+\t\t// In that case, utilize the next node's relative top to calculate the sticky node's position\n+\t\tif (lastChildRelativeTop === null && this.view.firstVisibleIndex === lastDescendantIndex && lastDescendantIndex + 1 < this.view.length) {\n+\t\t\tconst nodeHeight = this.treeDelegate.getHeight(this.view.element(lastDescendantIndex));\n+\t\t\tconst nextNodeRelativeTop = this.view.getRelativeTop(lastDescendantIndex + 1);\n+\t\t\tlastChildRelativeTop = nextNodeRelativeTop ? nextNodeRelativeTop - nodeHeight / this.view.renderHeight : null;\n+\t\t}\n+\n+\t\tif (lastChildRelativeTop === null) {\n+\t\t\treturn stickyRowPositionTop;\n+\t\t}\n+\n+\t\tconst lastChildNode = this.view.element(lastDescendantIndex);\n+\t\tconst lastChildHeight = this.treeDelegate.getHeight(lastChildNode);\n+\t\tconst topOfLastChild = lastChildRelativeTop * this.view.renderHeight;\n+\t\tconst bottomOfLastChild = topOfLastChild + lastChildHeight;\n+\n+\t\tif (stickyRowPositionTop > topOfLastChild && stickyRowPositionTop <= bottomOfLastChild) {\n+\t\t\treturn topOfLastChild;\n+\t\t}\n+\n+\t\treturn stickyRowPositionTop;\n+\t}\n+\n+\tprivate getParentNode(node: ITreeNode<T, TFilterData>): ITreeNode<T, TFilterData> | undefined {\n+\t\tconst nodeLocation = this.model.getNodeLocation(node);\n+\t\tconst parentLocation = this.model.getParentNodeLocation(nodeLocation);\n+\t\treturn parentLocation ? this.model.getNode(parentLocation) : undefined;\n+\t}\n+\n+\tprivate nodeIsUncollapsedParent(node: ITreeNode<T, TFilterData>): boolean {\n+\t\tconst nodeLocation = this.model.getNodeLocation(node);\n+\t\treturn this.model.getListRenderCount(nodeLocation) > 1;\n+\t}\n+\n+\tprivate getNodeIndex(node: ITreeNode<T, TFilterData>, nodeLocation?: TRef): number {\n+\t\tif (nodeLocation === undefined) {\n+\t\t\tnodeLocation = this.model.getNodeLocation(node);\n+\t\t}\n+\t\tconst nodeIndex = this.model.getListIndex(nodeLocation);\n+\t\treturn nodeIndex;\n+\t}\n+\n+\tprivate getNodeRange(node: ITreeNode<T, TFilterData>): { startIndex: number; endIndex: number } {\n+\t\tconst nodeLocation = this.model.getNodeLocation(node);\n+\t\tconst startIndex = this.model.getListIndex(nodeLocation);\n+\n+\t\tif (startIndex < 0) {\n+\t\t\tthrow new Error('Node not found in tree');\n+\t\t}\n+\n+\t\tconst renderCount = this.model.getListRenderCount(nodeLocation);\n+\t\tconst endIndex = startIndex + renderCount - 1;\n+\n+\t\treturn { startIndex, endIndex };\n+\t}\n+\n+\tupdateOptions(optionsUpdate: IAbstractTreeOptionsUpdate = {}): void {\n+\t\tconst validatedOptions = this.validateStickySettings(optionsUpdate);\n+\t\tif (this.maxNumberOfStickyElements !== validatedOptions.stickyScrollMaxItemCount) {\n+\t\t\tthis.maxNumberOfStickyElements = validatedOptions.stickyScrollMaxItemCount;\n+\t\t\tthis.update();\n+\t\t}\n+\t}\n+\n+\tvalidateStickySettings(options: IAbstractTreeOptionsUpdate): { stickyScrollMaxItemCount: number } {\n+\t\tlet stickyScrollMaxItemCount = 5;\n+\t\tif (typeof options.stickyScrollMaxItemCount === 'number') {\n+\t\t\tstickyScrollMaxItemCount = Math.max(options.stickyScrollMaxItemCount, 1);\n+\t\t}\n+\t\treturn { stickyScrollMaxItemCount };\n+\t}\n+}\n+\n+class StickyScrollWidget<T, TFilterData, TRef> implements IDisposable {\n+\n+\tprivate readonly _rootDomNode: HTMLElement;\n+\tprivate _previousState: StickyScrollState<T, TFilterData, TRef> | undefined;\n+\tprivate _mouseUpTimerDisposable = new MutableDisposable();\n+\n+\tconstructor(\n+\t\tcontainer: HTMLElement,\n+\t\tprivate readonly view: List<ITreeNode<T, TFilterData>>,\n+\t\tprivate readonly model: ITreeModel<T, TFilterData, TRef>,\n+\t\tprivate readonly treeRenderers: TreeRenderer<T, TFilterData, TRef, any>[],\n+\t\tprivate readonly treeDelegate: IListVirtualDelegate<ITreeNode<T, TFilterData>>\n+\t) {\n+\n+\t\tthis._rootDomNode = document.createElement('div');\n+\t\tthis._rootDomNode.classList.add('monaco-tree-sticky-container');\n+\t\tthis._rootDomNode.classList.add('monaco-list');\n+\t\tcontainer.appendChild(this._rootDomNode);\n+\t}\n+\n+\tsetState(state: StickyScrollState<T, TFilterData, TRef> | undefined): void {\n+\n+\t\tconst wasVisible = !!this._previousState && this._previousState.count > 0;\n+\t\tconst isVisible = !!state && state.count > 0;\n+\n+\t\t// If state has not changed, do nothing\n+\t\tif ((!wasVisible && !isVisible) || (wasVisible && isVisible && this._previousState!.equal(state!))) {\n+\t\t\treturn;\n+\t\t}\n+\n+\t\t// Update visibility of the widget if changed\n+\t\tif (wasVisible !== isVisible) {\n+\t\t\tthis.setVisible(isVisible);\n+\t\t}\n+\n+\t\t// Remove previous state\n+\t\tthis._previousState?.dispose();\n+\t\tthis._previousState = state;\n+\n+\t\tif (!isVisible) {\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tfor (let stickyIndex = 0; stickyIndex < state.count; stickyIndex++) {\n+\t\t\tconst stickyNode = state.stickyNodes[stickyIndex];\n+\t\t\tconst previousStickyNode = stickyIndex ? state.stickyNodes[stickyIndex - 1] : undefined;\n+\t\t\tconst currentWidgetHieght = previousStickyNode ? previousStickyNode.position + previousStickyNode.height : 0;\n+\n+\t\t\tconst { element, disposable } = this.createElement(stickyNode, currentWidgetHieght);\n+\n+\t\t\tif (stickyIndex === state.count - 1) {\n+\t\t\t\telement.classList.add('last-sticky');\n+\t\t\t}\n+\n+\t\t\tthis._rootDomNode.appendChild(element);\n+\t\t\tstate.addDisposable(disposable);\n+\t\t}\n+\n+\t\t// Add shadow element to the end of the widget\n+\t\tconst shadow = $('.monaco-tree-sticky-container-shadow');\n+\t\tthis._rootDomNode.appendChild(shadow);\n+\t\tstate.addDisposable(toDisposable(() => shadow.remove()));\n+\n+\t\t// Set the height of the widget to the bottom of the last sticky node\n+\t\tconst lastStickyNode = state.stickyNodes[state.count - 1];\n+\t\tthis._rootDomNode.style.height = `${lastStickyNode.position + lastStickyNode.height}px`;\n+\t}\n+\n+\tprivate createElement(stickyNode: StickyScrollNode<T, TFilterData>, currentWidgetHeight: number): { element: HTMLElement; disposable: IDisposable } {\n+\n+\t\tconst nodeLocation = this.model.getNodeLocation(stickyNode.node);\n+\t\tconst nodeIndex = this.model.getListIndex(nodeLocation);\n+\n+\t\t// Sticky element container\n+\t\tconst stickyElement = document.createElement('div');\n+\t\tstickyElement.style.top = `${stickyNode.position}px`;\n+\n+\t\tstickyElement.classList.add('monaco-tree-sticky-row');\n+\t\tstickyElement.classList.add('monaco-list-row');\n+\n+\t\tstickyElement.setAttribute('data-index', `${nodeIndex}`);\n+\t\tstickyElement.setAttribute('data-parity', nodeIndex % 2 === 0 ? 'even' : 'odd');\n+\t\tstickyElement.setAttribute('id', this.view.getElementID(nodeIndex));\n+\n+\t\t// Get the renderer for the node\n+\t\tconst nodeTemplateId = this.treeDelegate.getTemplateId(stickyNode.node);\n+\t\tconst renderer = this.treeRenderers.find((renderer) => renderer.templateId === nodeTemplateId);\n+\t\tif (!renderer) {\n+\t\t\tthrow new Error(`No renderer found for template id ${nodeTemplateId}`);\n+\t\t}\n+\n+\t\tconst nodeCopy = new Proxy(stickyNode.node, {});\n+\n+\t\t// Render the element\n+\t\tconst templateData = renderer.renderTemplate(stickyElement);\n+\t\trenderer.renderElement(nodeCopy, stickyNode.startIndex, templateData, stickyNode.height);\n+\n+\t\tconst mouseListenerDisposable = this.registerMouseListeners(stickyElement, stickyNode, currentWidgetHeight);\n+\n+\t\t// Remove the element from the DOM when state is disposed\n+\t\tconst disposable = toDisposable(() => {\n+\t\t\trenderer.disposeElement(nodeCopy, stickyNode.startIndex, templateData, stickyNode.height);\n+\t\t\trenderer.disposeTemplate(templateData);\n+\t\t\tmouseListenerDisposable.dispose();\n+\t\t\tstickyElement.remove();\n+\t\t});\n+\n+\t\treturn { element: stickyElement, disposable };\n+\t}\n+\n+\tprivate registerMouseListeners(stickyElement: HTMLElement, stickyNode: StickyScrollNode<T, TFilterData>, currentWidgetHeight: number): IDisposable {\n+\n+\t\treturn addDisposableListener(stickyElement, 'mouseup', (e: MouseEvent) => {\n+\t\t\tconst isRightClick = e.button === 2;\n+\t\t\tif (isRightClick) {\n+\t\t\t\treturn;\n+\t\t\t}\n+\n+\t\t\tif (isMonacoCustomToggle(e.target as HTMLElement) || isActionItem(e.target as HTMLElement)) {\n+\t\t\t\treturn;\n+\t\t\t}\n+\n+\t\t\t// Timeout 0 ensures that the tree handles the click event first\n+\t\t\tthis._mouseUpTimerDisposable.value = disposableTimeout(() => {\n+\t\t\t\tconst elementTop = this.view.getElementTop(stickyNode.startIndex);\n+\t\t\t\t// We can't rely on the current sticky node's position\n+\t\t\t\t// because the node might be partially scrolled under the widget\n+\t\t\t\tconst previousStickyNodeBottom = currentWidgetHeight;\n+\t\t\t\tthis.view.scrollTop = elementTop - previousStickyNodeBottom;\n+\t\t\t\tthis.view.setFocus([stickyNode.startIndex]);\n+\t\t\t\tthis.view.setSelection([stickyNode.startIndex]);\n+\t\t\t}, 0);\n+\t\t});\n+\t}\n+\n+\tprivate setVisible(visible: boolean): void {\n+\t\tthis._rootDomNode.style.display = visible ? 'block' : 'none';\n+\t}\n+\n+\tdispose(): void {\n+\t\tthis._mouseUpTimerDisposable.dispose();\n+\t\tthis._previousState?.dispose();\n+\t\tthis._rootDomNode.remove();\n+\t}\n+}\n+\n function asTreeMouseEvent<T>(event: IListMouseEvent<ITreeNode<T, any>>): ITreeMouseEvent<T> {\n \tlet target: TreeMouseEventTarget = TreeMouseEventTarget.Unknown;\n \n@@ -1212,6 +1612,8 @@ export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions {\n \treadonly fastScrollSensitivity?: number;\n \treadonly expandOnDoubleClick?: boolean;\n \treadonly expandOnlyOnTwistieClick?: boolean | ((e: any) => boolean); // e is T\n+\treadonly enableStickyScroll?: boolean;\n+\treadonly stickyScrollMaxItemCount?: number;\n }\n \n export interface IAbstractTreeOptions<T, TFilterData = void> extends IAbstractTreeOptionsUpdate, IListOptions<T> {\n@@ -1377,24 +1779,30 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<\n \t\tconst target = e.browserEvent.target as HTMLElement;\n \t\tconst onTwistie = target.classList.contains('monaco-tl-twistie')\n \t\t\t|| (target.classList.contains('monaco-icon-label') && target.classList.contains('folder-icon') && e.browserEvent.offsetX < 16);\n+\t\tconst isStickyElement = isStickyScrollElement(e.browserEvent.target as HTMLElement);\n \n \t\tlet expandOnlyOnTwistieClick = false;\n \n-\t\tif (typeof this.tree.expandOnlyOnTwistieClick === 'function') {\n+\t\tif (isStickyElement) {\n+\t\t\texpandOnlyOnTwistieClick = true;\n+\t\t}\n+\t\telse if (typeof this.tree.expandOnlyOnTwistieClick === 'function') {\n \t\t\texpandOnlyOnTwistieClick = this.tree.expandOnlyOnTwistieClick(node.element);\n \t\t} else {\n \t\t\texpandOnlyOnTwistieClick = !!this.tree.expandOnlyOnTwistieClick;\n \t\t}\n \n-\t\tif (expandOnlyOnTwistieClick && !onTwistie && e.browserEvent.detail !== 2) {\n-\t\t\treturn super.onViewPointer(e);\n-\t\t}\n+\t\tif (!isStickyElement) {\n+\t\t\tif (expandOnlyOnTwistieClick && !onTwistie && e.browserEvent.detail !== 2) {\n+\t\t\t\treturn super.onViewPointer(e);\n+\t\t\t}\n \n-\t\tif (!this.tree.expandOnDoubleClick && e.browserEvent.detail === 2) {\n-\t\t\treturn super.onViewPointer(e);\n+\t\t\tif (!this.tree.expandOnDoubleClick && e.browserEvent.detail === 2) {\n+\t\t\t\treturn super.onViewPointer(e);\n+\t\t\t}\n \t\t}\n \n-\t\tif (node.collapsible) {\n+\t\tif (node.collapsible && (!isStickyElement || onTwistie)) {\n \t\t\tconst location = this.tree.getNodeLocation(node);\n \t\t\tconst recursive = e.browserEvent.altKey;\n \t\t\tthis.tree.setFocus([location]);\n@@ -1407,7 +1815,9 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<\n \t\t\t}\n \t\t}\n \n-\t\tsuper.onViewPointer(e);\n+\t\tif (!isStickyElement) {\n+\t\t\tsuper.onViewPointer(e);\n+\t\t}\n \t}\n \n \tprotected override onDoubleClick(e: IListMouseEvent<ITreeNode<T, TFilterData>>): void {\n@@ -1524,13 +1934,15 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable\n \tprotected view: TreeNodeList<T, TFilterData, TRef>;\n \tprivate renderers: TreeRenderer<T, TFilterData, TRef, any>[];\n \tprotected model: ITreeModel<T, TFilterData, TRef>;\n+\tprivate treeDelegate: ComposedTreeDelegate<T, ITreeNode<T, TFilterData>>;\n \tprivate focus: Trait<T>;\n \tprivate selection: Trait<T>;\n \tprivate anchor: Trait<T>;\n \tprivate eventBufferer = new EventBufferer();\n \tprivate findController?: FindController<T, TFilterData>;\n \treadonly onDidChangeFindOpenState: Event<boolean> = Event.None;\n \tprivate focusNavigationFilter: ((node: ITreeNode<T, TFilterData>) => boolean) | undefined;\n+\tprivate stickyScrollController?: StickyScrollController<T, TFilterData, TRef>;\n \tprivate styleElement: HTMLStyleElement;\n \tprotected readonly disposables = new DisposableStore();\n \n@@ -1584,7 +1996,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable\n \t\trenderers: ITreeRenderer<T, TFilterData, any>[],\n \t\tprivate _options: IAbstractTreeOptions<T, TFilterData> = {}\n \t) {\n-\t\tconst treeDelegate = new ComposedTreeDelegate<T, ITreeNode<T, TFilterData>>(delegate);\n+\t\tthis.treeDelegate = new ComposedTreeDelegate<T, ITreeNode<T, TFilterData>>(delegate);\n \n \t\tconst onDidChangeCollapseStateRelay = new Relay<ICollapseStateChangeEvent<T, TFilterData>>();\n \t\tconst onDidChangeActiveNodes = new Relay<ITreeNode<T, TFilterData>[]>();\n@@ -1606,7 +2018,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable\n \t\tthis.focus = new Trait(() => this.view.getFocusedElements()[0], _options.identityProvider);\n \t\tthis.selection = new Trait(() => this.view.getSelectedElements()[0], _options.identityProvider);\n \t\tthis.anchor = new Trait(() => this.view.getAnchorElement(), _options.identityProvider);\n-\t\tthis.view = new TreeNodeList(_user, container, treeDelegate, this.renderers, this.focus, this.selection, this.anchor, { ...asListOptions(() => this.model, _options), tree: this });\n+\t\tthis.view = new TreeNodeList(_user, container, this.treeDelegate, this.renderers, this.focus, this.selection, this.anchor, { ...asListOptions(() => this.model, _options), tree: this });\n \n \t\tthis.model = this.createModel(_user, this.view, _options);\n \t\tonDidChangeCollapseStateRelay.input = this.model.onDidChangeCollapseState;\n@@ -1668,6 +2080,10 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable\n \t\t\tthis.onDidChangeFindMatchType = Event.None;\n \t\t}\n \n+\t\tif (_options.enableStickyScroll) {\n+\t\t\tthis.stickyScrollController = new StickyScrollController(this, this.model, this.view, this.renderers, this.treeDelegate, _options);\n+\t\t}\n+\n \t\tthis.styleElement = createStyleSheet(this.view.getHTMLElement());\n \t\tthis.getHTMLElement().classList.toggle('always', this._options.renderIndentGuides === RenderIndentGuides.Always);\n \t}\n@@ -1681,6 +2097,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable\n \n \t\tthis.view.updateOptions(this._options);\n \t\tthis.findController?.updateOptions(optionsUpdate);\n+\t\tthis.updateStickyScroll(optionsUpdate);\n \n \t\tthis._onDidUpdateOptions.fire(this._options);\n \n@@ -1691,6 +2108,16 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable\n \t\treturn this._options;\n \t}\n \n+\tprivate updateStickyScroll(optionsUpdate: IAbstractTreeOptionsUpdate) {\n+\t\tif (!this.stickyScrollController && this._options.enableStickyScroll) {\n+\t\t\tthis.stickyScrollController = new StickyScrollController(this, this.model, this.view, this.renderers, this.treeDelegate, this._options);\n+\t\t} else if (this.stickyScrollController && !this._options.enableStickyScroll) {\n+\t\t\tthis.stickyScrollController.dispose();\n+\t\t\tthis.stickyScrollController = undefined;\n+\t\t}\n+\t\tthis.stickyScrollController?.updateOptions(optionsUpdate);\n+\t}\n+\n \tupdateWidth(element: TRef): void {\n \t\tconst index = this.model.getListIndex(element);\n \n@@ -1801,6 +2228,10 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable\n \t\t\tcontent.push(`.monaco-list${suffix} .monaco-tl-indent > .indent-guide.active { border-color: ${styles.treeIndentGuidesStroke}; }`);\n \t\t}\n \n+\t\tif (styles.listBackground) {\n+\t\t\tcontent.push(`.monaco-list${suffix} .monaco-scrollable-element .monaco-tree-sticky-container .monaco-tree-sticky-row { background-color: ${styles.listBackground}; }`);\n+\t\t}\n+\n \t\tthis.styleElement.textContent = content.join('\\n');\n \n \t\tthis.view.style(styles);\n@@ -2086,6 +2517,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable\n \n \tdispose(): void {\n \t\tdispose(this.disposables);\n+\t\tthis.stickyScrollController?.dispose();\n \t\tthis.view.dispose();\n \t}\n }",
			"@@ -128,3 +128,39 @@\n .monaco-tree-type-filter-actionbar .monaco-action-bar .action-label {\n \tpadding: 2px;\n }\n+\n+.monaco-scrollable-element .monaco-tree-sticky-container{\n+\tposition: absolute;\n+\ttop: 0;\n+\tleft: 0;\n+\twidth: 100%;\n+\theight: 0;\n+\n+\tbackground-color: var(--vscode-sideBar-background);\n+}\n+\n+.monaco-scrollable-element .monaco-tree-sticky-container .monaco-tree-sticky-row{\n+\tposition: absolute;\n+\twidth: 100%;\n+\n+\tbackground-color: var(--vscode-sideBar-background);\n+\tz-index: 13;\n+}\n+\n+.monaco-scrollable-element .monaco-tree-sticky-container .monaco-tree-sticky-row.last-sticky{\n+\tz-index: 12;\n+}\n+\n+.monaco-scrollable-element .monaco-tree-sticky-container .monaco-tree-sticky-row:hover{\n+\tbackground-color: var(--vscode-list-hoverBackground) !important;\n+\tcursor: pointer;\n+}\n+\n+.monaco-scrollable-element .monaco-tree-sticky-container .monaco-tree-sticky-container-shadow{\n+\tposition: absolute;\n+\tbottom: -3px;\n+\tleft: 0px;\n+\theight: 3px;\n+\twidth: 100%;\n+\tbox-shadow: var(--vscode-scrollbar-shadow) 0 6px 6px -6px inset;\n+}",
			"@@ -189,6 +189,8 @@ const listSmoothScrolling = 'workbench.list.smoothScrolling';\n const mouseWheelScrollSensitivityKey = 'workbench.list.mouseWheelScrollSensitivity';\n const fastScrollSensitivityKey = 'workbench.list.fastScrollSensitivity';\n const treeExpandMode = 'workbench.tree.expandMode';\n+const treeStickyScroll = 'workbench.tree.enableStickyScroll';\n+const treeStickyScrollMaxElements = 'workbench.tree.stickyScrollMaxItemCount';\n \n function useAltAsMultipleSelectionModifier(configurationService: IConfigurationService): boolean {\n \treturn configurationService.getValue(multiSelectModifierSettingKey) === 'alt';\n@@ -1167,6 +1169,8 @@ function workbenchTreeDataPreamble<T, TFilterData, TOptions extends IAbstractTre\n \t\t\texpandOnlyOnTwistieClick: options.expandOnlyOnTwistieClick ?? (configurationService.getValue<'singleClick' | 'doubleClick'>(treeExpandMode) === 'doubleClick'),\n \t\t\tcontextViewProvider: contextViewService as IContextViewProvider,\n \t\t\tfindWidgetStyles: defaultFindWidgetStyles,\n+\t\t\tenableStickyScroll: Boolean(configurationService.getValue(treeStickyScroll)),\n+\t\t\tstickyScrollMaxItemCount: Number(configurationService.getValue(treeStickyScrollMaxElements)),\n \t\t} as TOptions\n \t};\n }\n@@ -1313,6 +1317,14 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {\n \t\t\t\tif (e.affectsConfiguration(treeExpandMode) && options.expandOnlyOnTwistieClick === undefined) {\n \t\t\t\t\tnewOptions = { ...newOptions, expandOnlyOnTwistieClick: configurationService.getValue<'singleClick' | 'doubleClick'>(treeExpandMode) === 'doubleClick' };\n \t\t\t\t}\n+\t\t\t\tif (e.affectsConfiguration(treeStickyScroll)) {\n+\t\t\t\t\tconst enableStickyScroll = configurationService.getValue<boolean>(treeStickyScroll);\n+\t\t\t\t\tnewOptions = { ...newOptions, enableStickyScroll };\n+\t\t\t\t}\n+\t\t\t\tif (e.affectsConfiguration(treeStickyScrollMaxElements)) {\n+\t\t\t\t\tconst stickyScrollMaxItemCount = Math.max(1, configurationService.getValue<number>(treeStickyScrollMaxElements));\n+\t\t\t\t\tnewOptions = { ...newOptions, stickyScrollMaxItemCount };\n+\t\t\t\t}\n \t\t\t\tif (e.affectsConfiguration(mouseWheelScrollSensitivityKey)) {\n \t\t\t\t\tconst mouseWheelScrollSensitivity = configurationService.getValue<number>(mouseWheelScrollSensitivityKey);\n \t\t\t\t\tnewOptions = { ...newOptions, mouseWheelScrollSensitivity };\n@@ -1465,6 +1477,16 @@ configurationRegistry.registerConfiguration({\n \t\t\tdefault: 'singleClick',\n \t\t\tdescription: localize('expand mode', \"Controls how tree folders are expanded when clicking the folder names. Note that some trees and lists might choose to ignore this setting if it is not applicable.\"),\n \t\t},\n+\t\t[treeStickyScroll]: {\n+\t\t\ttype: 'boolean',\n+\t\t\tdefault: 'false',\n+\t\t\tdescription: localize('sticky scroll', \"Controls whether sticky scrolling is enabled in trees.\"),\n+\t\t},\n+\t\t[treeStickyScrollMaxElements]: {\n+\t\t\ttype: 'number',\n+\t\t\tdefault: 5,\n+\t\t\tmarkdownDescription: localize('sticky scroll maximum items', \"Controls the number of sticky elements displayed in the tree when `#workbench.tree.enableStickyScroll#` is enabled.\"),\n+\t\t},\n \t\t[typeNavigationModeSettingKey]: {\n \t\t\ttype: 'string',\n \t\t\tenum: ['automatic', 'trigger'],",
			"@@ -1093,7 +1093,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS\n \tprivate _actionRunner: MultipleSelectionActionRunner | undefined;\n \tprivate _hoverDelegate: IHoverDelegate;\n \tprivate _hasCheckbox: boolean = false;\n-\tprivate _renderedElements = new Map<string, { original: ITreeNode<ITreeItem, FuzzyScore>; rendered: ITreeExplorerTemplateData }>(); // tree item handle to template data\n+\tprivate _renderedElements = new Map<string, { original: ITreeNode<ITreeItem, FuzzyScore>; rendered: ITreeExplorerTemplateData }[]>(); // tree item handle to template data\n \n \tconstructor(\n \t\tprivate treeViewId: string,\n@@ -1268,17 +1268,18 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS\n \t\tthis.setAlignment(templateData.container, node);\n \t\tthis.treeViewsService.addRenderedTreeItemElement(node, templateData.container);\n \n-\t\t// remember rendered element\n-\t\tthis._renderedElements.set(element.element.handle, { original: element, rendered: templateData });\n+\t\t// remember rendered element, an element can be rendered multiple times\n+\t\tconst renderedItems = this._renderedElements.get(element.element.handle) ?? [];\n+\t\tthis._renderedElements.set(element.element.handle, [...renderedItems, { original: element, rendered: templateData }]);\n \t}\n \n \tprivate rerender() {\n \t\t// As we add items to the map during this call we can't directly use the map in the for loop\n \t\t// but have to create a copy of the keys first\n \t\tconst keys = new Set(this._renderedElements.keys());\n \t\tfor (const key of keys) {\n-\t\t\tconst value = this._renderedElements.get(key);\n-\t\t\tif (value) {\n+\t\t\tconst values = this._renderedElements.get(key) ?? [];\n+\t\t\tfor (const value of values) {\n \t\t\t\tthis.disposeElement(value.original, 0, value.rendered);\n \t\t\t\tthis.renderElement(value.original, 0, value.rendered);\n \t\t\t}\n@@ -1406,9 +1407,9 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS\n \t\t}\n \t\titems = items.concat(additionalItems);\n \t\titems.forEach(item => {\n-\t\t\tconst renderedItem = this._renderedElements.get(item.handle);\n-\t\t\tif (renderedItem) {\n-\t\t\t\trenderedItem.rendered.checkbox?.render(item);\n+\t\t\tconst renderedItems = this._renderedElements.get(item.handle);\n+\t\t\tif (renderedItems) {\n+\t\t\t\trenderedItems.forEach(renderedItems => renderedItems.rendered.checkbox?.render(item));\n \t\t\t}\n \t\t});\n \t\tthis._onDidChangeCheckboxState.fire(items);\n@@ -1417,7 +1418,19 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS\n \tdisposeElement(resource: ITreeNode<ITreeItem, FuzzyScore>, index: number, templateData: ITreeExplorerTemplateData): void {\n \t\ttemplateData.elementDisposable.clear();\n \n-\t\tthis._renderedElements.delete(resource.element.handle);\n+\t\tconst itemRenders = this._renderedElements.get(resource.element.handle) ?? [];\n+\t\tconst renderedIndex = itemRenders.findIndex(renderedItem => templateData === renderedItem.rendered);\n+\n+\t\tif (renderedIndex < 0) {\n+\t\t\tthrow new Error('Disposing unknown element');\n+\t\t}\n+\n+\t\tif (itemRenders.length === 1) {\n+\t\t\tthis._renderedElements.delete(resource.element.handle);\n+\t\t} else {\n+\t\t\titemRenders.splice(renderedIndex, 1);\n+\t\t}\n+\n \t\tthis.treeViewsService.removeRenderedTreeItemElement(resource.element);\n \n \t\ttemplateData.checkbox?.dispose();",
			"@@ -93,7 +93,7 @@ const identityProvider = {\n };\n \n export function getContext(focus: ExplorerItem[], selection: ExplorerItem[], respectMultiSelection: boolean,\n-\tcompressedNavigationControllerProvider: { getCompressedNavigationController(stat: ExplorerItem): ICompressedNavigationController | undefined }): ExplorerItem[] {\n+\tcompressedNavigationControllerProvider: { getCompressedNavigationController(stat: ExplorerItem): ICompressedNavigationController[] | undefined }): ExplorerItem[] {\n \n \tlet focusedStat: ExplorerItem | undefined;\n \tfocusedStat = focus.length ? focus[0] : undefined;\n@@ -103,13 +103,15 @@ export function getContext(focus: ExplorerItem[], selection: ExplorerItem[], res\n \t\tfocusedStat = undefined;\n \t}\n \n-\tconst compressedNavigationController = focusedStat && compressedNavigationControllerProvider.getCompressedNavigationController(focusedStat);\n+\tconst compressedNavigationControllers = focusedStat && compressedNavigationControllerProvider.getCompressedNavigationController(focusedStat);\n+\tconst compressedNavigationController = compressedNavigationControllers && compressedNavigationControllers.length ? compressedNavigationControllers[0] : undefined;\n \tfocusedStat = compressedNavigationController ? compressedNavigationController.current : focusedStat;\n \n \tconst selectedStats: ExplorerItem[] = [];\n \n \tfor (const stat of selection) {\n-\t\tconst controller = compressedNavigationControllerProvider.getCompressedNavigationController(stat);\n+\t\tconst controllers = compressedNavigationControllerProvider.getCompressedNavigationController(stat);\n+\t\tconst controller = controllers && controllers.length ? controllers[0] : undefined;\n \t\tif (controller && focusedStat && controller === compressedNavigationController) {\n \t\t\tif (stat === focusedStat) {\n \t\t\t\tselectedStats.push(stat);\n@@ -524,8 +526,8 @@ export class ExplorerView extends ViewPane implements IExplorerView {\n \t\tthis._register(this.tree.onDidChangeCollapseState(e => {\n \t\t\tconst element = e.node.element?.element;\n \t\t\tif (element) {\n-\t\t\t\tconst navigationController = this.renderer.getCompressedNavigationController(element instanceof Array ? element[0] : element);\n-\t\t\t\tnavigationController?.updateCollapsed(e.node.collapsed);\n+\t\t\t\tconst navigationControllers = this.renderer.getCompressedNavigationController(element instanceof Array ? element[0] : element);\n+\t\t\t\tnavigationControllers?.forEach(controller => controller.updateCollapsed(e.node.collapsed));\n \t\t\t}\n \t\t\t// Update showing expand / collapse button\n \t\t\tthis.updateAnyCollapsedContext();\n@@ -594,13 +596,13 @@ export class ExplorerView extends ViewPane implements IExplorerView {\n \t\t// Adjust for compressed folders (except when mouse is used)\n \t\tif (anchor instanceof HTMLElement) {\n \t\t\tif (stat) {\n-\t\t\t\tconst controller = this.renderer.getCompressedNavigationController(stat);\n+\t\t\t\tconst controllers = this.renderer.getCompressedNavigationController(stat);\n \n-\t\t\t\tif (controller) {\n+\t\t\t\tif (controllers && controllers.length > 0) {\n \t\t\t\t\tif (DOM.isKeyboardEvent(e.browserEvent) || isCompressedFolderName(e.browserEvent.target)) {\n-\t\t\t\t\t\tanchor = controller.labels[controller.index];\n+\t\t\t\t\t\tanchor = controllers[0].labels[controllers[0].index];\n \t\t\t\t\t} else {\n-\t\t\t\t\t\tcontroller.last();\n+\t\t\t\t\t\tcontrollers.forEach(controller => controller.last());\n \t\t\t\t\t}\n \t\t\t\t}\n \t\t\t}\n@@ -615,8 +617,8 @@ export class ExplorerView extends ViewPane implements IExplorerView {\n \t\tconst roots = this.explorerService.roots; // If the click is outside of the elements pass the root resource if there is only one root. If there are multiple roots pass empty object.\n \t\tlet arg: URI | {};\n \t\tif (stat instanceof ExplorerItem) {\n-\t\t\tconst compressedController = this.renderer.getCompressedNavigationController(stat);\n-\t\t\targ = compressedController ? compressedController.current.resource : stat.resource;\n+\t\t\tconst compressedControllers = this.renderer.getCompressedNavigationController(stat);\n+\t\t\targ = compressedControllers && compressedControllers.length ? compressedControllers[0].current.resource : stat.resource;\n \t\t} else {\n \t\t\targ = roots.length === 1 ? roots[0].resource : {};\n \t\t}\n@@ -649,15 +651,17 @@ export class ExplorerView extends ViewPane implements IExplorerView {\n \t\t\tthis.resourceMoveableToTrash.reset();\n \t\t}\n \n-\t\tconst compressedNavigationController = stat && this.renderer.getCompressedNavigationController(stat);\n+\t\tconst compressedNavigationControllers = stat && this.renderer.getCompressedNavigationController(stat);\n \n-\t\tif (!compressedNavigationController) {\n+\t\tif (!compressedNavigationControllers) {\n \t\t\tthis.compressedFocusContext.set(false);\n \t\t\treturn;\n \t\t}\n \n \t\tthis.compressedFocusContext.set(true);\n-\t\tthis.updateCompressedNavigationContextKeys(compressedNavigationController);\n+\t\tcompressedNavigationControllers.forEach(controller => {\n+\t\t\tthis.updateCompressedNavigationContextKeys(controller);\n+\t\t});\n \t}\n \n \t// General methods\n@@ -870,9 +874,11 @@ export class ExplorerView extends ViewPane implements IExplorerView {\n \t\t\treturn;\n \t\t}\n \n-\t\tconst compressedNavigationController = this.renderer.getCompressedNavigationController(focused[0])!;\n-\t\tcompressedNavigationController.previous();\n-\t\tthis.updateCompressedNavigationContextKeys(compressedNavigationController);\n+\t\tconst compressedNavigationControllers = this.renderer.getCompressedNavigationController(focused[0])!;\n+\t\tcompressedNavigationControllers.forEach(controller => {\n+\t\t\tcontroller.previous();\n+\t\t\tthis.updateCompressedNavigationContextKeys(controller);\n+\t\t});\n \t}\n \n \tnextCompressedStat(): void {\n@@ -881,9 +887,11 @@ export class ExplorerView extends ViewPane implements IExplorerView {\n \t\t\treturn;\n \t\t}\n \n-\t\tconst compressedNavigationController = this.renderer.getCompressedNavigationController(focused[0])!;\n-\t\tcompressedNavigationController.next();\n-\t\tthis.updateCompressedNavigationContextKeys(compressedNavigationController);\n+\t\tconst compressedNavigationControllers = this.renderer.getCompressedNavigationController(focused[0])!;\n+\t\tcompressedNavigationControllers.forEach(controller => {\n+\t\t\tcontroller.next();\n+\t\t\tthis.updateCompressedNavigationContextKeys(controller);\n+\t\t});\n \t}\n \n \tfirstCompressedStat(): void {\n@@ -892,9 +900,11 @@ export class ExplorerView extends ViewPane implements IExplorerView {\n \t\t\treturn;\n \t\t}\n \n-\t\tconst compressedNavigationController = this.renderer.getCompressedNavigationController(focused[0])!;\n-\t\tcompressedNavigationController.first();\n-\t\tthis.updateCompressedNavigationContextKeys(compressedNavigationController);\n+\t\tconst compressedNavigationControllers = this.renderer.getCompressedNavigationController(focused[0])!;\n+\t\tcompressedNavigationControllers.forEach(controller => {\n+\t\t\tcontroller.first();\n+\t\t\tthis.updateCompressedNavigationContextKeys(controller);\n+\t\t});\n \t}\n \n \tlastCompressedStat(): void {\n@@ -903,9 +913,11 @@ export class ExplorerView extends ViewPane implements IExplorerView {\n \t\t\treturn;\n \t\t}\n \n-\t\tconst compressedNavigationController = this.renderer.getCompressedNavigationController(focused[0])!;\n-\t\tcompressedNavigationController.last();\n-\t\tthis.updateCompressedNavigationContextKeys(compressedNavigationController);\n+\t\tconst compressedNavigationControllers = this.renderer.getCompressedNavigationController(focused[0])!;\n+\t\tcompressedNavigationControllers.forEach(controller => {\n+\t\t\tcontroller.last();\n+\t\t\tthis.updateCompressedNavigationContextKeys(controller);\n+\t\t});\n \t}\n \n \tprivate updateCompressedNavigationContextKeys(controller: ICompressedNavigationController): void {",
			"@@ -276,7 +276,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu\n \n \tprivate config: IFilesConfiguration;\n \tprivate configListener: IDisposable;\n-\tprivate compressedNavigationControllers = new Map<ExplorerItem, CompressedNavigationController>();\n+\tprivate compressedNavigationControllers = new Map<ExplorerItem, CompressedNavigationController[]>();\n \n \tprivate _onDidChangeActiveDescendant = new EventMultiplexer<void>();\n \treadonly onDidChangeActiveDescendant = this._onDidChangeActiveDescendant.event;\n@@ -455,7 +455,9 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu\n \n \t\t\tconst compressedNavigationController = new CompressedNavigationController(id, node.element.elements, templateData, node.depth, node.collapsed);\n \t\t\ttemplateData.elementDisposables.add(compressedNavigationController);\n-\t\t\tthis.compressedNavigationControllers.set(stat, compressedNavigationController);\n+\n+\t\t\tconst nodeControllers = this.compressedNavigationControllers.get(stat) ?? [];\n+\t\t\tthis.compressedNavigationControllers.set(stat, [...nodeControllers, compressedNavigationController]);\n \n \t\t\t// accessibility\n \t\t\ttemplateData.elementDisposables.add(this._onDidChangeActiveDescendant.add(compressedNavigationController.onDidChange));\n@@ -468,7 +470,20 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu\n \t\t\t\t}\n \t\t\t}));\n \n-\t\t\ttemplateData.elementDisposables.add(toDisposable(() => this.compressedNavigationControllers.delete(stat)));\n+\t\t\ttemplateData.elementDisposables.add(toDisposable(() => {\n+\t\t\t\tconst nodeControllers = this.compressedNavigationControllers.get(stat) ?? [];\n+\t\t\t\tconst renderedIndex = nodeControllers.findIndex(controller => controller === compressedNavigationController);\n+\n+\t\t\t\tif (renderedIndex < 0) {\n+\t\t\t\t\tthrow new Error('Disposing unknown navigation controller');\n+\t\t\t\t}\n+\n+\t\t\t\tif (nodeControllers.length === 1) {\n+\t\t\t\t\tthis.compressedNavigationControllers.delete(stat);\n+\t\t\t\t} else {\n+\t\t\t\t\tnodeControllers.splice(renderedIndex, 1);\n+\t\t\t\t}\n+\t\t\t}));\n \t\t}\n \n \t\t// Input Box\n@@ -662,7 +677,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu\n \t\ttemplateData.templateDisposables.dispose();\n \t}\n \n-\tgetCompressedNavigationController(stat: ExplorerItem): ICompressedNavigationController | undefined {\n+\tgetCompressedNavigationController(stat: ExplorerItem): ICompressedNavigationController[] | undefined {\n \t\treturn this.compressedNavigationControllers.get(stat);\n \t}\n \n@@ -689,8 +704,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu\n \t}\n \n \tgetActiveDescendantId(stat: ExplorerItem): string | undefined {\n-\t\tconst compressedNavigationController = this.compressedNavigationControllers.get(stat);\n-\t\treturn compressedNavigationController?.currentId;\n+\t\treturn this.compressedNavigationControllers.get(stat)?.[0]?.currentId ?? undefined;\n \t}\n \n \tdispose(): void {",
			"@@ -150,7 +150,7 @@ export type FilterData = ResourceMarkersFilterData | MarkerFilterData | RelatedI\n \n export class ResourceMarkersRenderer implements ITreeRenderer<ResourceMarkers, ResourceMarkersFilterData, IResourceMarkersTemplateData> {\n \n-\tprivate renderedNodes = new Map<ITreeNode<ResourceMarkers, ResourceMarkersFilterData>, IResourceMarkersTemplateData>();\n+\tprivate renderedNodes = new Map<ResourceMarkers, IResourceMarkersTemplateData[]>();\n \tprivate readonly disposables = new DisposableStore();\n \n \tconstructor(\n@@ -185,25 +185,37 @@ export class ResourceMarkersRenderer implements ITreeRenderer<ResourceMarkers, R\n \t\t}\n \n \t\tthis.updateCount(node, templateData);\n-\t\tthis.renderedNodes.set(node, templateData);\n+\t\tconst nodeRenders = this.renderedNodes.get(resourceMarkers) ?? [];\n+\t\tthis.renderedNodes.set(resourceMarkers, [...nodeRenders, templateData]);\n \t}\n \n-\tdisposeElement(node: ITreeNode<ResourceMarkers, ResourceMarkersFilterData>): void {\n-\t\tthis.renderedNodes.delete(node);\n+\tdisposeElement(node: ITreeNode<ResourceMarkers, ResourceMarkersFilterData>, index: number, templateData: IResourceMarkersTemplateData): void {\n+\t\tconst nodeRenders = this.renderedNodes.get(node.element) ?? [];\n+\t\tconst nodeRenderIndex = nodeRenders.findIndex(nodeRender => templateData === nodeRender);\n+\n+\t\tif (nodeRenderIndex < 0) {\n+\t\t\tthrow new Error('Disposing unknown resource marker');\n+\t\t}\n+\n+\t\tif (nodeRenders.length === 1) {\n+\t\t\tthis.renderedNodes.delete(node.element);\n+\t\t} else {\n+\t\t\tnodeRenders.splice(nodeRenderIndex, 1);\n+\t\t}\n \t}\n \n \tdisposeTemplate(templateData: IResourceMarkersTemplateData): void {\n \t\ttemplateData.resourceLabel.dispose();\n \t}\n \n \tprivate onDidChangeRenderNodeCount(node: ITreeNode<ResourceMarkers, ResourceMarkersFilterData>): void {\n-\t\tconst templateData = this.renderedNodes.get(node);\n+\t\tconst nodeRenders = this.renderedNodes.get(node.element);\n \n-\t\tif (!templateData) {\n+\t\tif (!nodeRenders) {\n \t\t\treturn;\n \t\t}\n \n-\t\tthis.updateCount(node, templateData);\n+\t\tnodeRenders.forEach(nodeRender => this.updateCount(node, nodeRender));\n \t}\n \n \tprivate updateCount(node: ITreeNode<ResourceMarkers, ResourceMarkersFilterData>, templateData: IResourceMarkersTemplateData): void {",
			"@@ -830,7 +830,7 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID\n \t}\n \n \tprivate _revealInViewWithMinimalScrolling(viewIndex: number, firstLine?: boolean) {\n-\t\tconst firstIndex = this.view.firstVisibleIndex;\n+\t\tconst firstIndex = this.view.firstMostlyVisibleIndex;\n \t\tconst elementHeight = this.view.elementHeight(viewIndex);\n \n \t\tif (viewIndex <= firstIndex || (!firstLine && elementHeight >= this.view.renderHeight)) {",
			"@@ -165,7 +165,7 @@ export class ActionButtonRenderer implements ICompressibleTreeRenderer<ISCMActio\n \tstatic readonly TEMPLATE_ID = 'actionButton';\n \tget templateId(): string { return ActionButtonRenderer.TEMPLATE_ID; }\n \n-\tprivate actionButtons = new Map<ISCMActionButton, SCMActionButton>();\n+\tprivate actionButtons = new Map<ISCMActionButton, SCMActionButton[]>();\n \n \tconstructor(\n \t\t@ICommandService private commandService: ICommandService,\n@@ -194,8 +194,24 @@ export class ActionButtonRenderer implements ICompressibleTreeRenderer<ISCMActio\n \t\ttemplateData.actionButton.setButton(node.element.button);\n \n \t\t// Remember action button\n-\t\tthis.actionButtons.set(actionButton, templateData.actionButton);\n-\t\tdisposables.add({ dispose: () => this.actionButtons.delete(actionButton) });\n+\t\tconst renderedActionButtons = this.actionButtons.get(actionButton) ?? [];\n+\t\tthis.actionButtons.set(actionButton, [...renderedActionButtons, templateData.actionButton]);\n+\t\tdisposables.add({\n+\t\t\tdispose: () => {\n+\t\t\t\tconst renderedActionButtons = this.actionButtons.get(actionButton) ?? [];\n+\t\t\t\tconst renderedWidgetIndex = renderedActionButtons.findIndex(renderedActionButton => renderedActionButton === templateData.actionButton);\n+\n+\t\t\t\tif (renderedWidgetIndex < 0) {\n+\t\t\t\t\tthrow new Error('Disposing unknown action button');\n+\t\t\t\t}\n+\n+\t\t\t\tif (renderedActionButtons.length === 1) {\n+\t\t\t\t\tthis.actionButtons.delete(actionButton);\n+\t\t\t\t} else {\n+\t\t\t\t\trenderedActionButtons.splice(renderedWidgetIndex, 1);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t});\n \n \t\ttemplateData.disposable = disposables;\n \t}\n@@ -205,7 +221,7 @@ export class ActionButtonRenderer implements ICompressibleTreeRenderer<ISCMActio\n \t}\n \n \tfocusActionButton(actionButton: ISCMActionButton): void {\n-\t\tthis.actionButtons.get(actionButton)?.focus();\n+\t\tthis.actionButtons.get(actionButton)?.forEach(renderedActionButton => renderedActionButton.focus());\n \t}\n \n \tdisposeElement(node: ITreeNode<ISCMActionButton, FuzzyScore>, index: number, template: ActionButtonTemplate): void {\n@@ -286,7 +302,7 @@ class InputRenderer implements ICompressibleTreeRenderer<ISCMInput, FuzzyScore,\n \tstatic readonly TEMPLATE_ID = 'input';\n \tget templateId(): string { return InputRenderer.TEMPLATE_ID; }\n \n-\tprivate inputWidgets = new Map<ISCMInput, SCMInputWidget>();\n+\tprivate inputWidgets = new Map<ISCMInput, SCMInputWidget[]>();\n \tprivate contentHeights = new WeakMap<ISCMInput, number>();\n \tprivate editorSelections = new WeakMap<ISCMInput, Selection[]>();\n \n@@ -317,9 +333,23 @@ class InputRenderer implements ICompressibleTreeRenderer<ISCMInput, FuzzyScore,\n \t\ttemplateData.inputWidget.setInput(input);\n \n \t\t// Remember widget\n-\t\tthis.inputWidgets.set(input, templateData.inputWidget);\n+\t\tconst renderedWidgets = this.inputWidgets.get(input) ?? [];\n+\t\tthis.inputWidgets.set(input, [...renderedWidgets, templateData.inputWidget]);\n \t\ttemplateData.elementDisposables.add({\n-\t\t\tdispose: () => this.inputWidgets.delete(input)\n+\t\t\tdispose: () => {\n+\t\t\t\tconst renderedWidgets = this.inputWidgets.get(input) ?? [];\n+\t\t\t\tconst renderedWidgetIndex = renderedWidgets.findIndex(renderedWidget => renderedWidget === templateData.inputWidget);\n+\n+\t\t\t\tif (renderedWidgetIndex < 0) {\n+\t\t\t\t\tthrow new Error('Disposing unknown input widget');\n+\t\t\t\t}\n+\n+\t\t\t\tif (renderedWidgets.length === 1) {\n+\t\t\t\t\tthis.inputWidgets.delete(input);\n+\t\t\t\t} else {\n+\t\t\t\t\trenderedWidgets.splice(renderedWidgetIndex, 1);\n+\t\t\t\t}\n+\t\t\t}\n \t\t});\n \n \t\t// Widget cursor selections\n@@ -380,23 +410,27 @@ class InputRenderer implements ICompressibleTreeRenderer<ISCMInput, FuzzyScore,\n \t\treturn (this.contentHeights.get(input) ?? InputRenderer.DEFAULT_HEIGHT) + 10;\n \t}\n \n-\tgetRenderedInputWidget(input: ISCMInput): SCMInputWidget | undefined {\n+\tgetRenderedInputWidget(input: ISCMInput): SCMInputWidget[] | undefined {\n \t\treturn this.inputWidgets.get(input);\n \t}\n \n \tgetFocusedInput(): ISCMInput | undefined {\n-\t\tfor (const [input, inputWidget] of this.inputWidgets) {\n-\t\t\tif (inputWidget.hasFocus()) {\n-\t\t\t\treturn input;\n+\t\tfor (const [input, inputWidgets] of this.inputWidgets) {\n+\t\t\tfor (const inputWidget of inputWidgets) {\n+\t\t\t\tif (inputWidget.hasFocus()) {\n+\t\t\t\t\treturn input;\n+\t\t\t\t}\n \t\t\t}\n \t\t}\n \n \t\treturn undefined;\n \t}\n \n \tclearValidation(): void {\n-\t\tfor (const [, inputWidget] of this.inputWidgets) {\n-\t\t\tinputWidget.clearValidation();\n+\t\tfor (const [, inputWidgets] of this.inputWidgets) {\n+\t\t\tfor (const inputWidget of inputWidgets) {\n+\t\t\t\tinputWidget.clearValidation();\n+\t\t\t}\n \t\t}\n \t}\n }\n@@ -2592,10 +2626,12 @@ export class SCMViewPane extends ViewPane {\n \t\t} else if (isSCMInput(e.element)) {\n \t\t\tthis.scmViewService.focus(e.element.repository);\n \n-\t\t\tconst widget = this.inputRenderer.getRenderedInputWidget(e.element);\n+\t\t\tconst widgets = this.inputRenderer.getRenderedInputWidget(e.element);\n \n-\t\t\tif (widget) {\n-\t\t\t\twidget.focus();\n+\t\t\tif (widgets) {\n+\t\t\t\tfor (const widget of widgets) {\n+\t\t\t\t\twidget.focus();\n+\t\t\t\t}\n \t\t\t\tthis.tree.setFocus([], e.browserEvent);\n \n \t\t\t\tconst selection = this.tree.getSelection();\n@@ -2890,7 +2926,7 @@ export class SCMViewPane extends ViewPane {\n \t\t\t}\n \n \t\t\tif (focusedInput) {\n-\t\t\t\tthis.inputRenderer.getRenderedInputWidget(focusedInput)?.focus();\n+\t\t\t\tthis.inputRenderer.getRenderedInputWidget(focusedInput)?.forEach(widget => widget.focus());\n \t\t\t}\n \n \t\t\tthis.updateRepositoryCollapseAllContextKeys();\n@@ -2945,10 +2981,12 @@ export class SCMViewPane extends ViewPane {\n \t\tif (this.isExpanded()) {\n \t\t\tif (this.tree.getFocus().length === 0) {\n \t\t\t\tfor (const repository of this.scmViewService.visibleRepositories) {\n-\t\t\t\t\tconst widget = this.inputRenderer.getRenderedInputWidget(repository.input);\n+\t\t\t\t\tconst widgets = this.inputRenderer.getRenderedInputWidget(repository.input);\n \n-\t\t\t\t\tif (widget) {\n-\t\t\t\t\t\twidget.focus();\n+\t\t\t\t\tif (widgets) {\n+\t\t\t\t\t\tfor (const widget of widgets) {\n+\t\t\t\t\t\t\twidget.focus();\n+\t\t\t\t\t\t}\n \t\t\t\t\t\treturn;\n \t\t\t\t\t}\n \t\t\t\t}"
		];

		const result = await generator.provideTitleAndDescription({ commitMessages, patches }, CancellationToken.None);
		assert(result?.description);
		assertNoFakeIssue(result.description);
	});
});
